﻿<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
	xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
	xmlns:ical="http://www.ietf.org/rfc/rfc2445.txt"
	xmlns:itip="http://www.ietf.org/rfc/rfc2446.txt"
	xmlns="http://www.ietf.org/rfc/rcf2445.txt">
  <!-- ======================================================================== -->
  <!-- ics2xml.xsl XSL transformation of iCalendar .ics file to XML file        -->
  <!-- Tim Hare, November 2006                                                  -->
  <!-- This transformation may be freely used, attribution is appreciated       -->
  <!-- ======================================================================== -->


  <!-- define output format                                                     -->
  <xsl:output method="xml" media-type="xml/calendar" indent="yes" />

  <!--                                                                          -->
  <!-- global parameters of use throughout the stylesheet                       -->
  <!--                                                                          -->
  <xsl:variable name="newline">
    <xsl:text>
</xsl:text>
  </xsl:variable>
  <xsl:variable name="linewrap">
    <xsl:text>
 </xsl:text>
  </xsl:variable>
  <xsl:variable name="CR">&#13;</xsl:variable>
  <xsl:variable name="colon">
    <xsl:text>:</xsl:text>
  </xsl:variable>
  <xsl:variable name="semicolon">
    <xsl:value-of select="concat('&#59;','')" />
  </xsl:variable>

  <!--<xsl:apply-templates select="*" />-->
  <xsl:template match="/">
    <xsl:element name="iCalendar_Stream">
      <xsl:call-template name="convert_ics">
        <!-- "parameter" is the entire input file -->
        <xsl:with-param name="icaldata">
          <!--                                                                  -->
          <!-- First we adjust the whitespace in the incoming document .ics     -->
          <!-- file. Can NOT use normalize-space() or document() because they   -->
          <!-- remove the the CR-LF sequence crucial to recognizing the end of  -->
          <!-- things in iCalendar                                              -->
          <!--                                                                  -->
          <xsl:call-template name="space_adjust">
            <xsl:with-param name="icsinput">
              <xsl:value-of select="current()" />
            </xsl:with-param>
          </xsl:call-template>
        </xsl:with-param>
      </xsl:call-template>
    </xsl:element>
  </xsl:template>

  <!-- Process recursively.                                       -->
  <!-- Find Calendars and Components (BEGIN/END pairs)            -->
  <!-- and then process properties with parameters                -->
  <!-- try find BEGIN: then extract name and search for END:$name -->
  <xsl:template name="convert_ics">
    <xsl:param name="icaldata" />
    <xsl:choose>
      <xsl:when test="$icaldata = $newline">
        <!-- do nothing if it's only newline -->
      </xsl:when>
      <xsl:when test="starts-with($icaldata,'BEGIN:')">
        <xsl:variable name="element_name">
          <xsl:value-of select="substring-before(substring-after($icaldata,'BEGIN:'),$newline)" />
        </xsl:variable>
        <xsl:choose>
          <xsl:when test="substring-before($icaldata,concat('END:',$element_name))">
            <xsl:value-of select="$newline" />
            <xsl:element name="{concat($element_name,'')}">
              <xsl:call-template name="convert_ics">
                <xsl:with-param name="icaldata">
                  <xsl:value-of select="substring-before(substring-after($icaldata,concat('BEGIN:',$element_name,$newline)),concat('END:',$element_name,$newline))" />
                </xsl:with-param>
              </xsl:call-template>
            </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          </xsl:otherwise>
        </xsl:choose>
        <xsl:choose>
          <xsl:when test="substring-after($icaldata,concat('END:',$element_name,$newline))" >
            <xsl:call-template name="convert_ics">
              <xsl:with-param name="icaldata">
                <xsl:value-of select="substring-after($icaldata,concat('END:',$element_name,$newline))" />
              </xsl:with-param>
            </xsl:call-template>
          </xsl:when>
          <xsl:otherwise>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:otherwise>
        <!-- It isn't a BEGIN/END pair, so it's a property                                  -->
        <!-- each property becomes an element, each parameter an attribute of it            -->
        <xsl:variable name="element_name">
          <xsl:choose>
            <!-- if there's a semicolon, then the name is the string before the semicolon -->
            <xsl:when test="contains(substring-before($icaldata,$colon),$semicolon)" >
              <xsl:value-of select="substring-before($icaldata,$semicolon)" />
            </xsl:when>
            <!-- otherwise the name is the string before the colon -->
            <xsl:otherwise>
              <xsl:value-of select="substring-before($icaldata,$colon)" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:variable>
        <!--                                                                                -->
        <!--         Now create the elements                                                -->
        <!--                                                                                -->
        <xsl:value-of select="$newline" />
        <xsl:element name="{string($element_name)}" >
          <!--                                                                             -->
          <!--      If there is a semicolon, then we have property parameters which we     -->
          <!--      need to make into attributes of the element                            -->
          <!--                                                                             -->
          <xsl:if test="contains(substring-before($icaldata,$colon),$semicolon)" >
            <xsl:call-template name="process_parameters">
              <xsl:with-param name="parameters">
                <!--- everything from the first semicolon to the colon are properties   -->
                <xsl:value-of select="substring-after(substring-before($icaldata,$colon),$semicolon)" />
              </xsl:with-param>
            </xsl:call-template>
          </xsl:if>
          <!--                                                                             -->
          <!--  contents of the element are everything after the colon                     -->
          <!--  precede it with white space, to make it look better                        -->
          <!--                                                                             -->
          <xsl:value-of select="substring-before(substring-after($icaldata,$colon),$newline)" />
        </xsl:element>
        <xsl:if test="substring-after($icaldata,$newline)">
          <xsl:call-template name="convert_ics">
            <xsl:with-param name="icaldata">
              <xsl:value-of select="substring-after($icaldata,$newline)" />
            </xsl:with-param>
          </xsl:call-template>
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="process_parameters">
    <!--- recursively strip out parameters and make attributes of them -->
    <xsl:param name="parameters" />
    <xsl:choose>
      <!-- if more than one parameter present they are separated by semicolons -->
      <xsl:when test="substring-before($parameters,$semicolon)">
        <xsl:attribute name="{substring-before(substring-before($parameters,$semicolon),'=')}">
          <xsl:value-of select="substring-after(substring-before($parameters,$semicolon),'=')" />
        </xsl:attribute>
        <xsl:call-template name="process_parameters">
          <xsl:with-param name="parameters">
            <xsl:value-of select="substring-after($parameters,$semicolon)" />
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:if test="contains($parameters,'=')">
          <xsl:attribute name="{substring-before($parameters,'=')}">
            <xsl:value-of select="substring-after($parameters,'=')" />
          </xsl:attribute>
        </xsl:if>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="space_adjust">
    <!--                                                                                           -->
    <!-- This template removes leading spaces, and line-wrap sequences from the input, returning a -->
    <!-- variable which may still contain CR-LF sequences but not CR-LF-space                      -->
    <!-- it is invoked once as the entire iCalendar input file is turned into a variable           -->
    <xsl:param name="icsinput" />
    <xsl:variable name="special_case">
      <xsl:value-of select="concat($newline,'BEGIN:')" />
    </xsl:variable>
    <xsl:choose>
      <xsl:when test="starts-with($icsinput,$special_case)">
        <!--  Special case      -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,$newline)" />
            <!-- strip leading NL -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <!--                                                                                        -->
      <!-- To hold down recursion depth and iterations, test for different lengths of leading     -->
      <!-- spaces before recursively calling this routine                                         -->
      <xsl:when test="starts-with($icsinput,'     ')">
        <!--  five spaces                          -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,'     ')" />
            <!-- strip five       -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="starts-with($icsinput,'    ')">
        <!--  four spaces                          -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,'    ')" />
            <!-- strip four        -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="starts-with($icsinput,'   ')">
        <!--  three spaces                         -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,'   ')" />
            <!-- strip three       -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="starts-with($icsinput,'  ')">
        <!--  two spaces                           -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,'  ')" />
            <!-- strip two         -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:when test="starts-with($icsinput,' ')">
        <!--  one space                            -->
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <xsl:value-of select="substring-after($icsinput,' ')" />
            <!-- strip one         -->
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <!--                                                                                        -->
      <!-- At this point all leading spaces should be gone, so we look for linewraps to remove    -->
      <!--                                                                                        -->
      <xsl:when test="substring-before($icsinput,$linewrap)">
        <xsl:call-template name="space_adjust">
          <xsl:with-param name="icsinput">
            <!-- replace the linewrap with a single space                                     -->
            <xsl:value-of select="concat(substring-before($icsinput,$linewrap),' ',substring-after($icsinput,$linewrap))" />
          </xsl:with-param>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$icsinput" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>